﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace ParserCombinatorLibrary
{
    public class TokenizedGrammar<TFlag> : Grammar<Token<TFlag>>
        where TFlag : IComparable
    {
        public static Parser<Token<TFlag>, Token<TFlag>> Tok(TFlag flag)
        {
            return
                from t in Next()
                where t.Type.CompareTo(flag) == 0
                select t
                ;
        }

        public static Parser<Token<TFlag>, Token<TFlag>> Str(string content)
        {
            return
                from t in Next()
                where t.Content == content
                select t
                ;
        }

        private List<Tuple<TFlag, bool, Regex>> tokenCheckers = new List<Tuple<TFlag, bool, Regex>>();

        public void Define(TFlag flag, string regex)
        {
            tokenCheckers.Add(Tuple.Create(flag, true, new Regex(regex)));
        }

        public void Discard(TFlag flag, string regex)
        {
            tokenCheckers.Add(Tuple.Create(flag, false, new Regex(regex)));
        }

        public Token<TFlag>[] Tokenize(string input, out int firstError)
        {
            firstError = -1;
            List<Token<TFlag>> tokens = new List<Token<TFlag>>();

            int index = 0;
            while (index < input.Length)
            {
                var match = this.tokenCheckers
                    .Select(t => Tuple.Create(t.Item1, t.Item2, t.Item3.Match(input, index)))
                    .Where(t => t.Item3.Success && t.Item3.Index == index)
                    .FirstOrDefault();
                if (match != null)
                {
                    if (match.Item2)
                    {
                        tokens.Add(new Token<TFlag>
                        {
                            Type = match.Item1,
                            Content = match.Item3.Value,
                        });
                    }
                    index += match.Item3.Length;
                }
                else
                {
                    firstError = index;
                    return null;
                }
            }

            return tokens.ToArray();
        }
    }

    public struct Token<TFlag>
    {
        public TFlag Type { get; set; }
        public string Content { get; set; }
    }
}
